package cn.com.infcn.monitor.filter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Locale;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

import cn.com.infcn.monitor.core.MonitorCore;
import cn.com.infcn.monitor.core.MonitorProcessor;
import cn.com.infcn.monitor.listener.IFCSessionInformations;
import cn.com.infcn.monitor.timertask.MonitorLogTask;
import cn.com.infcn.monitor.util.IFCFileUtils;
import cn.com.infcn.monitor.util.IFCMonitorConstanst;
import cn.com.infcn.monitor.util.IFCMonitorUtils;
import cn.com.infcn.monitor.util.IFCWebUtils;
import cn.com.infcn.monitor.util.LibraryPath;
import cn.com.infcn.monitor.util.MonitorType;
import cn.com.infcn.monitor.web.Counter;
import cn.com.infcn.monitor.web.CounterServletResponseWrapper;
import cn.com.infcn.monitor.web.IFCCounterError;

public class MonitoringFilter implements Filter {
	
	private final static Logger LOGGER = Logger.getLogger(MonitoringFilter.class);
	
	private static Counter httpCounter = null;
	private static Counter errorCounter = null;

	public static final String version = "1.0.0";
	public static final Date START_DATE = new Date();

	private String filterRegex = "";
	private String filterSuffix = "";

	public MonitoringFilter() {
		super();
	}

	@Override
	public void init(FilterConfig config) throws ServletException {
		httpCounter = new Counter(Counter.HTTP_COUNTER_NAME);
		errorCounter = new Counter(Counter.ERROR_COUNTER_NAME);

		// 加载初始化参数
		initParam(config);

		// 启动定时任务，定时写监控日志信息
		MonitorLogTask task = new MonitorLogTask();
		task.startup();
	}

	@Override
	public void destroy() {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
			chain.doFilter(request, response);
			return;
		}
		final HttpServletRequest httpRequest = (HttpServletRequest) request;
		final HttpServletResponse httpResponse = (HttpServletResponse) response;

		String str = httpRequest.getServletPath();

		//请求页面资源
		if (str.startsWith("/"+IFCMonitorConstanst.MONITOR_PAGE_PATH_NAME)) {
			FileProcessor processor = new FileProcessor(httpRequest, httpResponse);
			processor.execute();
		}
		//请求监控接口数据
		else if(str.startsWith("/"+IFCMonitorConstanst.MONITORING_SERVLET_PATH_NAME)){
			MonitorProcessor processor = new MonitorProcessor(httpRequest, httpResponse);
			try {
				processor.processor();
			} catch (Exception e) {
				LOGGER.warn(e.getMessage(), e);
			}
		}
		// 判断是否需要统计
		else if (isUrlFilter(httpRequest)) {
			chain.doFilter(request, response);
		} else {
			doFilter(chain, httpRequest, httpResponse);
		}

	}

	// 判断是否过滤此请求地址
	private boolean isUrlFilter(HttpServletRequest httpRequest) {
		// 过滤后缀
		String uri = httpRequest.getRequestURI().toLowerCase();
		for (String suffix : IFCMonitorConstanst.filterSuffixs) {
			if (uri.endsWith(suffix)) {
				return true;
			}
		}

		if (IFCMonitorUtils.isNotBlank(this.filterRegex)) {
			Pattern pattern = Pattern.compile(this.filterRegex);
			Matcher matcher = pattern.matcher(uri);
			// 字符串是否与正则表达式相匹配
			boolean rs = matcher.matches();
			if (rs) {
				return true;
			}
		}

		return false;
	}

	private void doFilter(FilterChain chain, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
			throws IOException, ServletException {
		// 重写response，进行统计相应流量大小等
		final CounterServletResponseWrapper wrappedResponse = new CounterServletResponseWrapper(httpResponse);

		// 开始时间
		final long start = System.currentTimeMillis();
		// cpu开始时间
		final long startCpuTime = IFCWebUtils.getCurrentThreadCpuTime();
		boolean systemError = false;
		Throwable systemException = null;
		// 请求地址
		String requestName = getRequestName(httpRequest);
		// 完整请求地址
		final String completeRequestName = getCompleteRequestName(httpRequest, true);

		try {
			// 当前线程中保存 IFCRequestContext 对象
			httpCounter.bindContext(requestName, completeRequestName, httpRequest.getRemoteUser(), startCpuTime);
			httpRequest.setAttribute(IFCCounterError.REQUEST_KEY, completeRequestName);
			IFCCounterError.bindRequest(httpRequest);

			chain.doFilter(httpRequest, wrappedResponse);
			wrappedResponse.flushBuffer();
		} catch (final Throwable t) { // NOPMD
			systemException = t;
			throwException(t);
		} finally {
			if (httpCounter == null) {
				return;
			}
			try {
				// 相应持续时间
				final long duration = Math.max(System.currentTimeMillis() - start, 0);
				// cpu执行时间
				final long cpuUsedMillis = (IFCWebUtils.getCurrentThreadCpuTime() - startCpuTime) / 1000000;

				// session存放用户信息相关信息
				putUserInfoInSession(httpRequest);

				if (systemException != null) {
					systemError = true;
					final StringWriter stackTrace = new StringWriter(200);
					systemException.printStackTrace(new PrintWriter(stackTrace));
					errorCounter.addRequestForSystemError(systemException.toString(), duration, cpuUsedMillis,
							stackTrace.toString());
				} else if (wrappedResponse.getCurrentStatus() >= 400) {
					systemError = true;
					errorCounter.addRequestForSystemError("Error" + wrappedResponse.getCurrentStatus(), duration,
							cpuUsedMillis, null);
				}

				final int responseSize = wrappedResponse.getDataLength();
				if (wrappedResponse.getCurrentStatus() == HttpServletResponse.SC_NOT_FOUND) {
					requestName = "Error404";
				}
				// 保存当前请求统计信息
				httpCounter.addRequest(requestName, duration, cpuUsedMillis, systemError, responseSize);
			} finally {
				httpCounter.unbindContext();
				IFCCounterError.unbindRequest();
			}
		}

	}

	/**
	 * 保存当前用户相关信息
	 * 
	 * @param httpRequest
	 */
	private void putUserInfoInSession(HttpServletRequest httpRequest) {
		final HttpSession session = httpRequest.getSession(false);
		if (session == null) {
			return;
		}
		// 访问者国家
		if (session.getAttribute(IFCSessionInformations.SESSION_COUNTRY_KEY) == null) {
			final Locale locale = httpRequest.getLocale();
			if (locale.getCountry().length() > 0) {
				session.setAttribute(IFCSessionInformations.SESSION_COUNTRY_KEY, locale.getCountry());
			} else {
				session.setAttribute(IFCSessionInformations.SESSION_COUNTRY_KEY, locale.getLanguage());
			}
		}
		if (session.getAttribute(IFCSessionInformations.SESSION_REMOTE_ADDR) == null) {
			// 访问者IP
			final String forwardedFor = httpRequest.getHeader("X-Forwarded-For");
			final String remoteAddr;
			if (forwardedFor == null) {
				remoteAddr = httpRequest.getRemoteAddr();
			} else {
				remoteAddr = httpRequest.getRemoteAddr() + " forwarded for " + forwardedFor;
			}
			session.setAttribute(IFCSessionInformations.SESSION_REMOTE_ADDR, remoteAddr);
		}
		// 访问用户
		if (session.getAttribute(IFCSessionInformations.SESSION_REMOTE_USER) == null) {
			final String remoteUser = httpRequest.getRemoteUser();
			if (remoteUser != null) {
				session.setAttribute(IFCSessionInformations.SESSION_REMOTE_USER, remoteUser);
			}
		}
	}

	/**
	 * 请求路径
	 * 
	 * @param request
	 * @return
	 */
	protected String getRequestName(HttpServletRequest request) {
		return getCompleteRequestName(request, false);
	}

	/**
	 * 请求路径+请求参数
	 * 
	 * @param httpRequest
	 * @param includeQueryString
	 * @return
	 */
	private static String getCompleteRequestName(HttpServletRequest httpRequest, boolean includeQueryString) {
		String tmp = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
		final int lastIndexOfSemiColon = tmp.lastIndexOf(';');
		if (lastIndexOfSemiColon != -1) {
			tmp = tmp.substring(0, lastIndexOfSemiColon);
		}
		final String method;
		if ("XMLHttpRequest".equals(httpRequest.getHeader("X-Requested-With"))) {
			method = "ajax " + httpRequest.getMethod();
		} else {
			method = httpRequest.getMethod();
		}
		// 不包含参数，直接返回
		if (!includeQueryString) {
			return tmp + ' ' + method;
		}
		final String queryString = httpRequest.getQueryString();
		if (queryString == null) {
			return tmp + ' ' + method;
		}
		return tmp + '?' + queryString + ' ' + method;
	}

	private static void throwException(Throwable t) throws IOException, ServletException {
		if (t instanceof Error) {
			throw (Error) t;
		} else if (t instanceof RuntimeException) {
			throw (RuntimeException) t;
		} else if (t instanceof IOException) {
			throw (IOException) t;
		} else if (t instanceof ServletException) {
			throw (ServletException) t;
		} else {
			throw new ServletException(t.getMessage(), t);
		}
	}

	public static Counter getHttpCounter() {
		return httpCounter;
	}

	public static Counter getErrorCounter() {
		return errorCounter;
	}
	
	
	private void initParam(FilterConfig config){
		System.out.println("--------IFCMonitorServlet.init------");
		// 加载监控目录信息
		System.out.println("--------load param : dirPath---------");
		String dirPath = config.getInitParameter("dirPath");
		if (dirPath != null && dirPath.length() > 0) {
			String[] paths = dirPath.split(",");
			for (String path : paths) {
				// 矫正路径，正斜杠反斜杠问题
				path = IFCFileUtils.correctPath(path);
				IFCMonitorConstanst.dirPaths.add(path);
				System.out.println(path);
			}
		}

		// 加载日志文件存储路径
		System.out.println("--------load param : dataPath-------");
		String dataPathTemp = config.getInitParameter("dataPath");
		if (dataPathTemp != null && !dataPathTemp.trim().equals("")) {
			// 矫正路径，正斜杠反斜杠问题
			dataPathTemp = IFCFileUtils.correctPath(dataPathTemp);

			char last = dataPathTemp.charAt(dataPathTemp.length() - 1);
			if (last != '\\' && last != '/') {
				dataPathTemp += File.separator;
			}
			// 监控日志文件存储路径
			IFCMonitorConstanst.dataPath = dataPathTemp;
		} else {
			// 默认路径 为： 项目根路径 /monitorlog/目录下
			ServletContext s1 = config.getServletContext();
			String temp = s1.getRealPath("/");
			char last = temp.charAt(temp.length() - 1);
			if (last != '\\' && last != '/') {
				temp += File.separator;
			}
			IFCMonitorConstanst.dataPath = temp + IFCMonitorConstanst.dataPathDefault;
		}
		System.out.println(IFCMonitorConstanst.dataPath);

		// 获取排除监控信息
		String excludeType = config.getInitParameter("excludeType");
		if (IFCMonitorUtils.isNotBlank(excludeType)) {
			String[] types = excludeType.split(",");

			EnumSet<MonitorType> enumSet = EnumSet.allOf(MonitorType.class);
			for (MonitorType monitorType : enumSet) {
				for (String t : types) {
					if (monitorType.getName().equals(t)) {
						IFCMonitorConstanst.excludeTypeList.add(t);
					}
				}
			}
		}

		// 获取监控信息
		String includeType = config.getInitParameter("includeType");
		if (IFCMonitorUtils.isNotBlank(includeType)) {
			String[] types = excludeType.split(",");

			EnumSet<MonitorType> enumSet = EnumSet.allOf(MonitorType.class);
			for (MonitorType monitorType : enumSet) {
				for (String t : types) {
					if (monitorType.getName().equals(t)) {
						IFCMonitorConstanst.includeTypeList.add(t);
					}
				}
			}
		}
		
		//定时任务间隔周期
		String monitorInterval = config.getInitParameter("monitorInterval");
		if(IFCMonitorUtils.isNotBlank(monitorInterval)){
			int interval = 0;
			try {
				interval = Integer.parseInt(monitorInterval);
			} catch (Exception e) {
			}
			if(interval>0){
				IFCMonitorConstanst.monitorInterval = interval;
			}
		}
		
		//过滤正则
		this.filterRegex = config.getInitParameter("filter-regex");
		//过滤后缀
		this.filterSuffix = config.getInitParameter("filter-suffix");

		if (IFCMonitorUtils.isNotBlank(filterSuffix)) {
			String[] suffixArr = filterSuffix.split(",");
			for (String s : suffixArr) {
				IFCMonitorConstanst.filterSuffixs.add(s);
			}
		} else {
			IFCMonitorConstanst.filterSuffixs.add(".jpg");
			IFCMonitorConstanst.filterSuffixs.add(".gif");
			IFCMonitorConstanst.filterSuffixs.add(".png");
			IFCMonitorConstanst.filterSuffixs.add(".ico");
			IFCMonitorConstanst.filterSuffixs.add(".jpeg");
			IFCMonitorConstanst.filterSuffixs.add(".css");
			IFCMonitorConstanst.filterSuffixs.add(".js");
		}
	}
}
